
/*-------------------------------------------------------------------------
 *
 * shippable.c
 *	  确定哪些数据库对象可以传输到远程服务器。
 *
 * 我们需要确定特定的函数、操作符以及数据类型是否可以传输到远程服务器执行——也就是说，
 * 它们是否存在，并且在远程的行为与本地相同？
 * 内置对象通常被认为是可传输的。其他对象如果由用户声明为可传输，则也可以被传输。
 *
 * 注意：还有其他过滤规则会阻止传输可变函数或使用不可移植排序的函数。
 * 这些考虑在此处无需考虑。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/postgres_fdw/shippable.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/transam.h"
#include "catalog/dependency.h"
#include "postgres_fdw.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/syscache.h"

/* 用于缓存可运输性查找结果的哈希表 */
static HTAB *ShippableCacheHash = NULL;

/*
 * 可运输性查找的哈希键。我们包括FDW服务器OID，因为决策可能因服务器而异。否则，对象通过其（本地！）OID和目录OID进行识别。
 */
typedef struct
{
	/* XXX 我们假设这个结构体不包含填充字节 */
	Oid			objid;			/* 函数/操作符/类型 OID */
	Oid			classid;		/* 其目录的OID（pg_proc等） */
	Oid			serverid;		/* 我们关注的FDW服务器 */
} ShippableCacheKey;

typedef struct
{
	ShippableCacheKey key;		/* 哈希键 - 必须放在第一位 */
	bool		shippable;
} ShippableCacheEntry;


/*
 * 当pg_foreign_server被更新时，刷新缓存条目。
 *
 * 我们这样做是因为有可能使用ALTER SERVER来更改服务器的扩展选项。尽管一旦为对象做出了可运输性决定，我们目前也不麻烦去检查对象的扩展成员资格是否发生了变化。
 */
static void fc_InvalidateShippableCacheCallback(Datum fc_arg, int fc_cacheid, uint32 fc_hashvalue)
{
	HASH_SEQ_STATUS fc_status;
	ShippableCacheEntry *fc_entry;

	/*
	 * 原则上，我们可以仅刷新与过期的pg_foreign_server条目相关的缓存条目；但那样会更复杂，而且可能不值得费心。所以现在，只需刷新所有条目。
	 */
	hash_seq_init(&fc_status, ShippableCacheHash);
	while ((fc_entry = (ShippableCacheEntry *) hash_seq_search(&fc_status)) != NULL)
	{
		if (hash_search(ShippableCacheHash,
						(void *) &fc_entry->key,
						HASH_REMOVE,
						NULL) == NULL)
			elog(ERROR, "hash table corrupted");
	}
}

/*
 * 初始化可运输性决策的后端生命周期缓存。
 */
static void fc_InitializeShippableCache(void)
{
	HASHCTL		fc_ctl;

	/* 创建哈希表。 */
	fc_ctl.keysize = sizeof(ShippableCacheKey);
	fc_ctl.entrysize = sizeof(ShippableCacheEntry);
	ShippableCacheHash =
		hash_create("Shippability cache", 256, &fc_ctl, HASH_ELEM | HASH_BLOBS);

	/* 设置pg_foreign_server上的失效回调。 */
	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
								  fc_InvalidateShippableCacheCallback,
								  (Datum) 0);
}

/*
 * 如果给定对象（操作符/函数/类型）根据服务器选项是可运输的，则返回true。
 *
 * 现在“可运输性”完全取决于对象是否属于用户声明的一个扩展。将来，我们还可以有一个逐个声明的函数/操作符列表。
 */
static bool fc_lookup_shippable(Oid fc_objectId, Oid fc_classId, PgFdwRelationInfo *fc_fpinfo)
{
	Oid			fc_extensionOid;

	/*
	 * 对象是某个扩展的成员吗？（注意：这是一个相当昂贵的查找，这就是我们尝试缓存结果的原因。）
	 */
	fc_extensionOid = getExtensionOfObject(fc_classId, fc_objectId);

	/* 如果是，该扩展是否在fpinfo->shippable_extensions中？ */
	if (OidIsValid(fc_extensionOid) &&
		list_member_oid(fc_fpinfo->shippable_extensions, fc_extensionOid))
		return true;

	return false;
}

/*
 * 如果给定对象是PostgreSQL的内置对象之一，则返回true。
 *
 * 我们使用FirstGenbkiObjectId作为分界，因此我们只考虑具有手动分配OID的对象为“内置”，而不是例如在information_schema中定义的任何函数或类型。
 *
 * 我们处理类型的约束比处理函数或操作符的约束更严格：我们希望只接受在pg_catalog中的类型，否则deparse_type_name可能会错误地未能对其名称进行模式限定。
 * 因此，我们必须排除information_schema类型。
 *
 * XXX 这有一个问题，即内置对象的集合随着时间的推移而扩大。对我们来说是内置的某些东西可能对远程服务器来说是未知的，如果它是旧版本。但是跟踪这一点将是一项巨大的工作。
 */
bool is_builtin(Oid fc_objectId)
{
	return (fc_objectId < FirstGenbkiObjectId);
}

/*
 * is_shippable
 *	   这个对象（函数/操作符/类型）可以运输到外部服务器吗？
 */
bool is_shippable(Oid fc_objectId, Oid fc_classId, PgFdwRelationInfo *fc_fpinfo)
{
	ShippableCacheKey fc_key;
	ShippableCacheEntry *fc_entry;

	/* 内置对象被认为是可运输的。 */
	if (is_builtin(fc_objectId))
		return true;

	/* 否则，如果用户没有指定任何可运输扩展，则放弃。 */
	if (fc_fpinfo->shippable_extensions == NIL)
		return false;

	/* 如果是第一次通过，则初始化缓存。 */
	if (!ShippableCacheHash)
		fc_InitializeShippableCache();

	/* 设置缓存哈希键 */
	fc_key.objid = fc_objectId;
	fc_key.classid = fc_classId;
	fc_key.serverid = fc_fpinfo->server->serverid;

	/* 查看我们是否已经缓存了结果。 */
	fc_entry = (ShippableCacheEntry *)
		hash_search(ShippableCacheHash,
					(void *) &fc_key,
					HASH_FIND,
					NULL);

	if (!fc_entry)
	{
		/* 在缓存中未找到，因此执行可发货性查找。 */
		bool		fc_shippable = fc_lookup_shippable(fc_objectId, fc_classId, fc_fpinfo);

		/*
		 * 在我们手中拥有可发货结果之前，不要创建新的哈希条目，
		 * 因为底层目录查找可能会触发缓存失效。
		 */
		fc_entry = (ShippableCacheEntry *)
			hash_search(ShippableCacheHash,
						(void *) &fc_key,
						HASH_ENTER,
						NULL);

		fc_entry->shippable = fc_shippable;
	}

	return fc_entry->shippable;
}
